﻿//IAtomic.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.BusinessEntities
{
	using System;
	using System.Collections.Generic;
	using System.Threading;

	using Ecng.Collections;

	/// <summary>
	/// Транзакция на изменения данных у объектов <see cref="IAtomic"/> (например, <see cref="Order"/> или <see cref="Security"/>).
	/// Создается при вызове метода <see cref="IAtomic.BeginUpdate"/>.
	/// </summary>
	public class UpdateTransaction : IDisposable
	{
		private readonly TransactionManager _manager;

		internal UpdateTransaction(TransactionManager manager)
		{
			if (manager == null)
				throw new ArgumentNullException("manager");

			_manager = manager;
		}

		/// <summary>
		/// Закончить транзакцию на изменения данных, запущенную через <see cref="IAtomic.BeginUpdate"/>.
		/// </summary>
		public void Dispose()
		{
			_manager.EndUpdate();
		}
	}

	/// <summary>
	/// Транзакция на чтение данных у объектов <see cref="IAtomic"/> (например, <see cref="Order"/> или <see cref="Security"/>).
	/// Создается при вызове метода <see cref="IAtomic.BeginRead"/>.
	/// </summary>
	public class ReadTransaction : IDisposable
	{
		private readonly TransactionManager _manager;
		private readonly bool _isUpgradable;

		internal ReadTransaction(TransactionManager manager, bool isUpgradable)
		{
			if (manager == null)
				throw new ArgumentNullException("manager");

			_manager = manager;
			_isUpgradable = isUpgradable;
		}

		/// <summary>
		/// Закончить транзакцию на чтение данных, запущенную через <see cref="IAtomic.BeginRead"/>.
		/// </summary>
		public void Dispose()
		{
			_manager.EndRead(_isUpgradable);
		}
	}

	/// <summary>
	/// Интерфейс для всех торговых объектов, которые поддерживают атомарную операцию обновления и чтения.
	/// </summary>
	public interface IAtomic
	{
		/// <summary>
		/// Начать обновление данных. Окончание обновление данных инициируется через <see cref="UpdateTransaction.Dispose"/>.
		/// </summary>
		/// <returns>Транзакция на изменения данных.</returns>
		UpdateTransaction BeginUpdate();

		/// <summary>
		/// Начать чтение данных. В период действия транзакции потоки данных не могут изменить внутреннее состояние объекта, из которого читаются данные.
		/// Окончание чтения данных инициируется через <see cref="ReadTransaction.Dispose"/>.
		/// </summary>
		/// <param name="isUpgradable">Можно ли вызвать внутри блокировки на чтение внутреннюю блокировку на запись.</param>
		/// <returns>Транзакция на чтение данных.</returns>
		ReadTransaction BeginRead(bool isUpgradable = false);
	}

	class TransactionManager : IAtomic
	{
		private readonly List<Action> _actions = new List<Action>();
		private readonly object _updateLock = new object();
		private UpdateTransaction _currentUpdateTransaction;
		private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();

		public UpdateTransaction BeginUpdate()
		{
			_rwLock.EnterWriteLock();

			if (_currentUpdateTransaction != null)
				throw new InvalidOperationException("Предыдушая транзакция не была закончена.");

			_currentUpdateTransaction = new UpdateTransaction(this);
			return _currentUpdateTransaction;
		}

		public void EndUpdate()
		{
			Action[] actions;

			lock (_updateLock)
			{
				_currentUpdateTransaction = null;
				actions = _actions.CopyAndClear();
			}

			_rwLock.ExitWriteLock();

			actions.ForEach(a => a());
		}

		public ReadTransaction BeginRead(bool isUpgradable)
		{
			if (isUpgradable)
				_rwLock.EnterUpgradeableReadLock();
			else
				_rwLock.EnterReadLock();

			return new ReadTransaction(this, isUpgradable);
		}

		public void EndRead(bool isUpgradable)
		{
			if (isUpgradable)
				_rwLock.ExitUpgradeableReadLock();
			else
				_rwLock.ExitReadLock();
		}

		public void Do(Action action)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			lock (_updateLock)
			{
				if (_currentUpdateTransaction != null)
				{
					_actions.Add(action);
					return;
				}
			}

			action();
		}
	}
}